﻿//////////////////////////////////////////////////////////////////
//
// Charles\Main.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Factory;
using Interfaces;
using Network;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Utilities;
using Ionic.Zip;
using System.Threading;
using System.Diagnostics;

namespace Charles
{
   public partial class Main : Form
   {
      ////////////////////////////////////////////////////////////////////////
      //
      private static Logger logger_;
      private NetworkServer network_;
      private static Regex regexHGT_;
      private static Regex regexConverted_;

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      static Main()
      {
         regexHGT_ = new Regex(@"^(?<latHem>[NS])(?<lat>\d+)(?<lonHem>[EW])(?<lon>\d+)\.HGT",
            RegexOptions.Compiled);
         regexConverted_ = new Regex(@"^(?<latHem>[NS])(?<lat>\d+)(?<lonHem>[EW])(?<lon>\d+)",
            RegexOptions.Compiled);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public Main()
      {
         InitializeComponent();
         
         // The network class also manages various GUI elements, as those
         // may be common to lots of the apps
         NetworkGui.GuiObjects gui = new NetworkGui.GuiObjects();
         gui.hostForm = this;
         gui.menuLogin = loginMenu;
         gui.menuLogout = logoutMenu;
         gui.statusBar = statusBar;
         gui.statusNetwork = statusNetwork;
         gui.statusText = statusText;
         gui.statusHeartbeat = statusHeartbeat;
         network_ = new NetworkServer(Program.senderId, gui);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void menuExit_Click(object sender, EventArgs e)
      {
         try
         {
            Close();
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// If Charles is not running, prompt to see if the user wants to start
      /// it
      /// </summary>
      private void checkCamilla(bool checkStarted)
      {
         bool createdNew = false;
         using (EventWaitHandle camillaRunning = new EventWaitHandle(false,
            EventResetMode.ManualReset, Identifiers.Camilla, out createdNew))
         {
            if (checkStarted)
            {
               if (!createdNew)
               {
                  // Already started
                  return;
               }
               StartStopOptions startup = Program.Settings.StartCamilla;
               bool start = false;
               switch (startup)
               {
                  case StartStopOptions.Always:
                     start = true;
                     break;

                  case StartStopOptions.Prompt:
                     start = (DialogResult.Yes == MessageBox.Show(this,
                        "Do you want to start Camilla, too?", Application.ProductName, MessageBoxButtons.YesNo));
                     break;

                  default:
                     break;
               }
               if (start)
               {
                  logger_.DebugFormat("Starting Camilla, too");
                  Process.Start("Camilla.exe");
               }
            }
            else
            {
               if (!createdNew)
               {
                  bool stop = false;
                  switch (Program.Settings.StopCamilla.Value)
                  {
                     case StartStopOptions.Always:
                        stop = true;
                        break;

                     case StartStopOptions.Prompt:
                        stop = (DialogResult.Yes == MessageBox.Show(this,
                           "Do you want to stop Camilla, too?", Application.ProductName, MessageBoxButtons.YesNo));
                        break;

                     default:
                        break;
                  }
                  if (stop)
                  {
                     logger_.DebugFormat("signalling Camilla to stop");
                     camillaRunning.Set();
                  }
               }
            }
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_Shown(object sender, EventArgs e)
      {
         try
         {
            // Start Camilla if it's not already started
            checkCamilla(true);

            // Get a network connection
            if (!network_.Initialize())
            {                          
               // This is pretty fatal :(
               MessageBox.Show(string.Concat(Application.ProductName, " failed to initialize network connection"),
                  Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
               Close();
               return;
            }

            // Start with a login
            network_.Login(Program.Settings.AutoLogin);

            // Enable the housekeeping
            guiTimer.Enabled = true;

            // run the planet
            network_.startPlanet("Earth");
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_Load(object sender, EventArgs e)
      {
         try
         {
            logger_ = Program.Logger;
            loggerViewer.Dock = DockStyle.Fill;

            // Now that we have started up, we can initialize all of the scripts
            // to define the behaviour of all of the entities in the game
            if (!Program.Almanac.Scripts.All(scriptFile => scriptFile.Initialize()))
            {
               logger_.ErrorFormat("Failed to initialize the scripts");
               Close();
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void handleResize(object sender, EventArgs e)
      {
         ListView view = sender as ListView;
         try
         {
            view = sender as ListView;
            if (null == view)
            {
               return;
            }
            view.BeginUpdate();

            int count = (null == view) ? 0 : view.Columns.Count;
            if (0 == count)
            {
               // Not ready
               return;
            }

            if (view == lvGalaxies)
            {
               // All columns the same width
               int equalWidth = view.ClientSize.Width / count;
               view.Columns.Cast<ColumnHeader>().ToList()
                  .ForEach(column => column.Width = equalWidth);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         finally
         {
            if (null != view)
            {
               view.EndUpdate();
            }
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void Main_FormClosing(object sender, FormClosingEventArgs e)
      {
         try
         {
            if (null != network_)
            {
               // Stop the planet running
               network_.stopPlanet();

               // And shut down the common stuff
               network_.Shutdown();
               network_ = null;
            }

            // Should we stop Camilla?
            checkCamilla(false);

            loggerViewer.Shutdown();
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void handleTick(object sender, EventArgs e)
      {
         try
         {
            // Should we shut down?
            if (Program.ProgramRunning.WaitOne(0))
            {
               logger_.DebugFormat("detected remote request to shut down");
               Close();
               return;
            }

            // Snapshot the time, so we use a consistent value throughout this method
            DateTime now = DateTime.Now;

            // Tell the network that we are still alive
            network_.Heartbeat(now);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      private string getRootFolder()
      {
         try
         {
            FolderBrowserDialog folder = new FolderBrowserDialog();
            folder.Description = "Root directory for the SRTM files";
            folder.SelectedPath = Program.Settings.SrtmFolder;
            folder.ShowNewFolderButton = false;
            if (DialogResult.OK != folder.ShowDialog(this))
            {
               return null;
            }
            Program.Settings.SrtmFolder.Value = folder.SelectedPath;
            return folder.SelectedPath;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return null;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Swap bytes in all of the HGT files in a given folder
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void swapBytesToolStripMenuItem_Click(object sender, EventArgs e)
      {
         try
         {
            string rootFolder = getRootFolder();
            if (null == rootFolder)
            {
               return;
            }
            srtmSwapBytes(rootFolder);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void copyOverlapToolStripMenuItem_Click(object sender, EventArgs e)
      {
         try
         {
            string rootFolder = getRootFolder();
            if (null == rootFolder)
            {
               return;
            }
            srtmCopyOverlaps(rootFolder);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Do all the conversions in order
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void combinedToolStripMenuItem_Click(object sender, EventArgs e)
      {
         try
         {
            string rootFolder = getRootFolder();
            if (null == rootFolder)
            {
               return;
            }
            srtmSwapBytes(rootFolder);
            srtmCopyOverlaps(rootFolder);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="rootFolder"></param>
      private void srtmSwapBytes(string rootFolder)
      {
         try
         {
            byte[] blankRow = new byte[Const.srtmRow3602 * 2];
            for (int column = 0; column < 2 * (SrtmTileStore.SRTM_POINTS_PER_ROW + 1); column += 2)
            {
               blankRow[column] = 0;
               blankRow[column + 1] = 0x80;
            }

            Queue<byte[]> buffers = new Queue<byte[]>();
            for (int index = 0; index < 4; index++)
            {
               byte[] buffer = new byte[2 * Const.srtmRow3602 * Const.srtmRow3602];
               Buffer.BlockCopy(blankRow, 0, buffer, 0, 2 * Const.srtmRow3602);
               for (int row = 1; row < Const.srtmRow3602; ++row)
               {
                  buffer[2 * Const.srtmRow3602 * row] = 0;
                  buffer[2 * Const.srtmRow3602 * row + 1] = 0x80;
               }
               buffers.Enqueue(buffer);
            }

            Regex regex = new Regex(@"[NS](?<lat>\d+)[EW](?<lon>\d+)\..*");
            List<string> filenames = Directory.EnumerateFiles(rootFolder, "*.overlapped", SearchOption.AllDirectories).ToList();
            filenames.Where(filename =>
               {
                  Match match = regex.Match(filename);
                  if (!match.Success)
                  {
                     return false;
                  }
                  int lat = int.Parse(match.Groups["lat"].Value);
                  int lon = int.Parse(match.Groups["lon"].Value);
                  return ((50 > lat) && (23 < lat) && (127 > lon) && (68 < lon));
               })
            //.AsParallel()
            .All(inputFilename =>
            {
               byte[] buffer;
               lock (this) buffer = buffers.Dequeue();
               using (FileStream input = new FileStream(inputFilename, FileMode.Open))
               {
                  input.Read(buffer, 0, 2 * Const.srtmRow3602 * Const.srtmRow3602);
                  
                  // Hack to skip over the starting area if it's filled with the VOID_DATA already swapped
                  bool skipRow = true;
                  for (int bufferIndex = 0; bufferIndex < 2 * Const.srtmRow3602; ++bufferIndex)
                  {
                     if (0 == (bufferIndex % 1))
                     {
                        if (buffer[bufferIndex] != 0x80)
                        {
                           skipRow = false;
                           break;
                        }
                     }
                     else if (buffer[bufferIndex] != 0)
                     {
                        skipRow = false;
                        break;
                     }
                  }
                  
                  byte tmp;
                  int startIndex = skipRow ? 1 : 0;
                  int offset = skipRow ? 2 * Const.srtmRow3602 : 0;
                  for (int row = startIndex; row < Const.srtmRow3602; ++row)
                  {
                     if (0 != startIndex)
                     {
                        offset += 2;
                     }
                     for (int column = startIndex; column < Const.srtmRow3602; ++column)
                     {
                        tmp = buffer[offset];
                        buffer[offset] = buffer[offset + 1];
                        buffer[offset + 1] = tmp;
                        offset += 2;
                     }
                  }
               }
               string outputFilename = Path.ChangeExtension(inputFilename, ".swapped");
               using (FileStream output = new FileStream(outputFilename, FileMode.OpenOrCreate))
               {
                  output.Write(buffer, 0, 2 * Const.srtmRow3602 * Const.srtmRow3602);
               }
               lock (this) buffers.Enqueue(buffer);
               logger_.DebugFormat("swapped '{0}' into '{1}'", inputFilename, outputFilename);
               return true;
            });
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="input"></param>
      /// <returns></returns>
      private void writeHeader(FileStream output, int maxHeight, byte[] encoding)
      {
         try
         {
            // 0 is below sea-level, 1 is sea-level, 255 is no data. All other values
            // have heights that are encoded to suit the range of values in each tile 
            ushort[] heights = new ushort[254];
            BinaryWriter writer = new BinaryWriter(output);
            writer.Write(maxHeight);
            output.Write(encoding, 0, encoding.Length);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="input"></param>
      /// <returns></returns>
      private int readHeaderSize(FileStream input)
      {
         try
         {
            byte lsbCount = (byte)input.ReadByte();
            byte msbCount = (byte)input.ReadByte();
            return (int)((int)msbCount * 256 | lsbCount) + 2;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return -1;
      }

      ////////////////////////////////////////////////////////////////////////
      List<Tuple<int, byte[], short>> srtmAddTuple(
         Dictionary<string, List<Tuple<int, byte[], short>>> padding, string filename)
      {
         List<Tuple<int, byte[], short>> tuple = null;
         if (File.Exists(filename))
         {
            lock (this)
            {
               if (!padding.TryGetValue(filename, out tuple))
               {
                  tuple = new List<Tuple<int, byte[], short>>(4000);
                  padding.Add(filename, tuple);
               }
            }
         }
         return tuple;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private bool srtmReadOverlap(string rootFolder,
         Dictionary<string, List<Tuple<int, byte[], short>>> padding,
         Tuple<string, string, int, string, int> tupleFile)
      {
         try
         {
            string inputFilename = tupleFile.Item1;
            string latHem = tupleFile.Item2;
            int lat = tupleFile.Item3;
            string lonHem = tupleFile.Item4;
            int lon = tupleFile.Item5;
            List<Tuple<int, byte[], short>> tuple;
            using (FileStream stream = new FileStream(inputFilename, FileMode.Open))
            {
               BinaryReader reader = new BinaryReader(stream);
               string rightHandFilename = Path.Combine(rootFolder,
                  String.Concat(latHem, lat.ToString("00"), lonHem, (lon - 1).ToString("000"),
                  ".padded"));

               // Get the tuple that applies to the target file, or create
               // one if we haven't already (no other thread has processed it, yet)
               if (null != (tuple = srtmAddTuple(padding, rightHandFilename)))
               {
                  // Copy the last-but-one word to the right-hand file
                  for (int row = 1; row < Const.srtmRow3601; ++row)
                  {
                     stream.Seek(2 * (Const.srtmRow3602 * row + Const.srtm3600), SeekOrigin.Begin);
                     lock (this)
                     {
                        tuple.Add(new Tuple<int, byte[], short>(2 * Const.srtmRow3602 * row, null, reader.ReadInt16()));
                     }
                  }
               }

               string belowFilename = Path.Combine(rootFolder,
                  String.Concat(latHem, (lat - 1).ToString("00"), lonHem, lon.ToString("000"),
                  ".padded"));
               if (null != (tuple = srtmAddTuple(padding, belowFilename)))
               {
                  // Copy the "nearly-bottom" row into the file below us
                  stream.Seek(2 * (Const.srtmRow3602 * Const.srtm3600 + 2), SeekOrigin.Begin);
                  byte[] lineOverlap = new byte[2 * Const.srtmRow3601];
                  stream.Read(lineOverlap, 0, lineOverlap.Length);
                  tuple.Insert(0, new Tuple<int, byte[], short>(2, lineOverlap, 0));
               }

               string belowRightFilename = Path.Combine(rootFolder,
                  String.Concat(latHem, (lat - 1).ToString("00"), lonHem, (lon - 1).ToString("000"),
                  ".padded"));
               if (null != (tuple = srtmAddTuple(padding, belowRightFilename)))
               {
                  // Copy the "nearly-bottom-right" word into the bottom-right file
                  stream.Seek(2 * Const.srtmRow3602 * Const.srtmRow3601 - 2, SeekOrigin.Begin);
                  tuple.Insert(0, new Tuple<int, byte[], short>(0, null, reader.ReadInt16()));
               }
            }
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      private void srtmWriteOverlap(string rootFolder,
         Dictionary<string, List<Tuple<int, byte[], short>>> padding)
      {
         try
         {
            long count = 0;
            padding.All(kvp =>
               {
                  string filename = kvp.Key;
                  List<Tuple<int, byte[], short>> tuples = kvp.Value;

                  string outputFilename = Path.ChangeExtension(filename, "overlapped");
                  if (File.Exists(outputFilename))
                  {
                     File.Delete(outputFilename);
                  }
                  File.Move(filename, outputFilename);

                  using (FileStream stream = new FileStream(outputFilename, FileMode.Open, FileAccess.Write, FileShare.Read))
                  {
                     BinaryWriter writer = new BinaryWriter(stream);
                     tuples.ForEach(tuple =>
                        {
                           int offset = tuple.Item1;
                           byte[] buffer = tuple.Item2;
                           short word = tuple.Item3;
                           stream.Seek(offset, SeekOrigin.Begin);

                           if (null != buffer)
                           {
                              stream.Write(buffer, 0, buffer.Length);
                           }
                           else
                           {
                              writer.Write(word);
                           }
                        });
                  }
                  Interlocked.Increment(ref count);
                  logger_.DebugFormat("{0}: written overlaps to '{1}' and renamed it to '{2}'", count, filename, outputFilename);
                  return true;
               });
            
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="rootFolder"></param>
      private void srtmCopyOverlaps(string rootFolder)
      {
         try
         {
            Dictionary<string, List<Tuple<int, byte[], short>>> padding = new Dictionary<string, List<Tuple<int, byte[], short>>>(1000);
            Regex regex = new Regex(@"(?<latHem>[NS])(?<lat>\d+)(?<lonHem>[EW])(?<lon>\d+)\..*");
            
            List<Tuple<string, string, int, string, int>> tupleFiles =
               Directory.EnumerateFiles(rootFolder, "*.padded", SearchOption.TopDirectoryOnly)
                  .Select<string, Tuple<string, string, int, string, int>>(filename =>
                  {
                     Match match = regex.Match(filename);
                     if (match.Success)
                     {
                        int lat = int.Parse(match.Groups["lat"].Value);
                        int lon = int.Parse(match.Groups["lon"].Value);
                        if ((50 > lat) && (23 < lat) && (127 > lon) && (68 < lon))
                        {
                           string latHem = match.Groups["latHem"].Value;
                           string lonHem = match.Groups["lonHem"].Value;
                           return new Tuple<string, string, int, string, int>(filename, latHem, lat, lonHem, lon);
                        }
                     }
                     return null;
                  })
                  .Where<Tuple<string, string, int, string, int>>(tuple => null != tuple)
                  .ToList();

            long count = 0;
            bool success = true;
            if (tupleFiles.AsParallel().All(tupleFile =>
               {
                  if (!success)
                  {
                     return false;
                  }
                  if (!srtmReadOverlap(rootFolder, padding, tupleFile))
                  {
                     // break all of the threads
                     return success = false;
                  }
                  Interlocked.Increment(ref count);
                  logger_.DebugFormat("{0}: read overlaps from '{1}'", count, tupleFile.Item1);
                  return true;
               }))
            {
               srtmWriteOverlap(rootFolder, padding);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="rootFolder"></param>
      private void srtmCountHeights(string rootFolder)
      {
         try
         {
            long[] counts = new long[65536];
            List<string> filenames = Directory.EnumerateFiles(rootFolder, "*.hgt", SearchOption.AllDirectories).ToList();
            filenames.AsParallel().All(filename =>
            {
               using (FileStream input = new FileStream(filename, FileMode.Open))
               {
                  byte[] buffer = new byte[(SrtmTileStore.SRTM_POINTS_PER_ROW + 1) * 2];
                  for (int row = 0; row < SrtmTileStore.SRTM_POINTS_PER_COLUMN; ++row)
                  {
                     input.Read(buffer, 2, SrtmTileStore.SRTM_POINTS_PER_ROW * 2);
                     for (int column = 1; column < SrtmTileStore.SRTM_POINTS_PER_ROW; ++column)
                     {
                        short height = (short)(((int)buffer[2 * column] << 8) | buffer[2 * column + 1]);
                        System.Threading.Interlocked.Increment(ref counts[32768 + height]);
                     }
                  }
                  logger_.DebugFormat("counted heights from '{0}'", filename);
               }
               return true;
            });

            // Output the totals of each height value
            StringBuilder sb = new StringBuilder();
            sb.Append("Frequency of heights across all SRTM files\r\n");
            for (int index = 0; index < counts.Length; ++index)
            {
               sb.Append((index - 32768).ToString("00000"));
               sb.Append(',');
               sb.Append(counts[index]);
               sb.Append("\r\n");
            }
            File.WriteAllText("statistics.csv", sb.ToString());
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void countHeightsToolStripMenuItem_Click(object sender, EventArgs e)
      {
         try
         {
            string rootFolder = getRootFolder();
            if (null == rootFolder)
            {
               return;
            }
            srtmCountHeights(rootFolder);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void camillaShutdownToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
      {
         camillaShutdownAlwaysToolStripMenuItem.Checked =
            camillaShutdownNeverToolStripMenuItem.Checked =
            camillaShutdownPromptToolStripMenuItem.Checked = false;
         switch (Program.Settings.StopCamilla.Value)
         {
            case StartStopOptions.Always:
               camillaShutdownAlwaysToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.Never:
               camillaShutdownNeverToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.Prompt:
               camillaShutdownPromptToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.None:
            default:
               break;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void camillaStartupToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
      {
         camillaStartupAlwaysToolStripMenuItem.Checked =
            camillaStartupNeverToolStripMenuItem.Checked =
            camillaStartupPromptToolStripMenuItem.Checked = false;
         switch (Program.Settings.StartCamilla.Value)
         {
            case StartStopOptions.Always:
               camillaStartupAlwaysToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.Never:
               camillaStartupNeverToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.Prompt:
               camillaStartupPromptToolStripMenuItem.Checked = true;
               break;

            case StartStopOptions.None:
            default:
               break;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void camillaShutdown_Click(object sender, EventArgs e)
      {
         Program.Settings.StopCamilla.Value =
            (camillaShutdownAlwaysToolStripMenuItem == sender) ? StartStopOptions.Always :
            (camillaShutdownNeverToolStripMenuItem == sender) ? StartStopOptions.Never :
            (camillaShutdownPromptToolStripMenuItem == sender) ? StartStopOptions.Prompt :
            StartStopOptions.None;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      private void camillaStartup_Click(object sender, EventArgs e)
      {
         Program.Settings.StartCamilla.Value =
            (camillaStartupAlwaysToolStripMenuItem == sender) ? StartStopOptions.Always :
            (camillaStartupNeverToolStripMenuItem == sender) ? StartStopOptions.Never :
            (camillaStartupPromptToolStripMenuItem == sender) ? StartStopOptions.Prompt :
            StartStopOptions.None;
      }
   }
}
